﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace ReliableSession.src
{
    public class FifoBuffer
    {
        public byte[] m_data;
        private BinaryReader  m_binaryReader;
        private BinaryWriter m_binaryWriter;

        public FifoBuffer(int bufferSize)
        {
            m_data = new byte[bufferSize];
            m_binaryReader = new BinaryReader(new MemoryStream(m_data));
            m_binaryWriter = new BinaryWriter(new MemoryStream(m_data));
            reset();
        }

        public void reset()
        {
            m_binaryReader.BaseStream.Position = 0;
            m_binaryReader.BaseStream.SetLength(0);
            m_binaryWriter.BaseStream.Position = 0;
        }

        public ushort remaining()
        {
            long diff = m_binaryWriter.BaseStream.Position - m_binaryReader.BaseStream.Position;
            if (diff < 0)
            {
                throw new System.IO.EndOfStreamException("Read/write pointers out of sync");
            }
            if(diff > ushort.MaxValue) 
            {
                throw new System.IO.IOException("Stream exceeds maximum limit of 65K");
            }
            ushort r = (ushort)diff;
            return r;
        }

        public ushort readUshort()
        {

            ushort data = m_binaryReader.ReadUInt16();
            return data;
        }

        public byte readByte()
        {
            byte data = m_binaryReader.ReadByte();
            return data;
        }

        public int read(byte[] data, int offset, int count)
        {
            Debug.Assert(count >= 0);
            int len = m_binaryReader.Read(data, offset, count);
            return len;
        }

        public void write(byte value)
        {
            extendReader(sizeof(byte));
            m_binaryWriter.Write(value);
            assertLength();
        }

        public void write(ushort value)
        {
            extendReader(sizeof(ushort));
            m_binaryWriter.Write(value);
            assertLength();
        }

        public void write(System.UInt64 value)
        {
            extendReader(sizeof(UInt64));
            m_binaryWriter.Write(value);
            assertLength();
        }

        public int write(byte[] values, int index, int count)
        {
            int len = extendReader(count);
            if (len > 0)
            {
                m_binaryWriter.Write(values, index, count);
                assertLength();
            }
            return len;
        }

        public int write(byte[] values,int startPos, int index, int count)
        {
            int len = 0;
            if (startPos == 0)
            {
                len = write(values, index, count);
            }
            else
            {
                long prev = m_binaryWriter.BaseStream.Position;
                m_binaryWriter.BaseStream.Position = startPos;
                m_binaryWriter.Write(values, index, count);
                m_binaryWriter.BaseStream.Position = prev;
            }
            return len;
        }
        private void assertLength()
        {
            Debug.Assert(m_binaryReader.BaseStream.Length == m_binaryWriter.BaseStream.Position);
        }

        private int extendReader(int numBytes)
        {
            long newLen = numBytes + m_binaryReader.BaseStream.Length;
            Debug.Assert(newLen >= 0);
            if (newLen > m_binaryWriter.BaseStream.Length)
            {
                return -1;
            }
            m_binaryReader.BaseStream.SetLength(newLen);
            return (int)newLen;
        }

        public void write(byte[] p)
        {
            this.write(p,0,p.Length);
        }

        public void compact()
        {
            Debug.Assert(m_binaryReader.BaseStream.Position <= m_binaryWriter.BaseStream.Position);
            int len = (int)(m_binaryWriter.BaseStream.Position - m_binaryReader.BaseStream.Position);
            int srcPosition = (int)m_binaryReader.BaseStream.Position;
            Buffer.BlockCopy(m_data,srcPosition,m_data, 0,len);
            m_binaryWriter.BaseStream.Position = len;
            m_binaryReader.BaseStream.SetLength(len);
            m_binaryReader.BaseStream.Position = 0;
        }

        /// <summary>
        /// Rewind the read pointer.
        /// </summary>
        /// <param name="amount"></param>
        public void rewind(int amount)
        {
            if (amount > m_binaryReader.BaseStream.Position)
            {
                m_binaryReader.BaseStream.Position = 0;
            }
            else
            {
                m_binaryReader.BaseStream.Position -= amount;
            }
        }

        internal int spaceAvailable()
        {
            int remaining = (int)(m_binaryWriter.BaseStream.Length - m_binaryWriter.BaseStream.Position);
            return remaining;
        }

        /// <summary>
        /// Return the number of bytes available
        /// </summary>
        /// <returns></returns>
        public int dataAvailable()
        {
            int available = (int)(m_binaryWriter.BaseStream.Position - m_binaryReader.BaseStream.Position);
            Debug.Assert(available >= 0);
            return available;
        }

        public void compact(int compactLen)
        {
            int available = dataAvailable();
            Debug.Assert(available > 0);
            Debug.Assert(compactLen >= 0);
            if (compactLen > available)
            {
                reset();
            }
            else
            {
                m_binaryReader.BaseStream.Position += compactLen;
                compact();
            }
        }

        public int readPosition()
        {
            long index = m_binaryReader.BaseStream.Position;
            Debug.Assert(index < int.MaxValue);
            return (int)index;
        }

        internal int writePosition()
        {
            return (int)m_binaryWriter.BaseStream.Position;

        }
    }
}
